Popular Searches
Popular Course Categories
Popular Courses

Flutter TextEditingController

Flutter TextEditingController

Flutter Forms & User Input


Flutter TextEditingController – Detailed Notes


TextEditingController is a Flutter class used to control and access the text entered into a TextField or TextFormField. It allows developers to read, modify, clear, and listen to changes in text input. Flutter's official documentation also recommends disposing of a controller when it is no longer needed. :contentReference[oaicite:0]{index=0}




1. What is TextEditingController?


A TextEditingController acts as a connection between your Dart code and a text input widget. Instead of only displaying a text field, the controller allows the application to programmatically interact with the text entered by the user.


It can be used to:



  • Read text entered by the user.

  • Set initial text.

  • Change text programmatically.

  • Clear the text field.

  • Listen for text changes.

  • Access the current selection.

  • Access the complete TextEditingValue.

  • Control text fields in login, registration, search, and other forms.


Flutter's API describes TextEditingController as a controller for an editable text field. It extends ValueNotifier, so listeners can react when its value changes. :contentReference[oaicite:1]{index=1}




2. Basic Syntax


final controller = TextEditingController();

Connect it to a TextField:


TextField(
  controller: controller,
)

After connecting the controller, the entered text can be retrieved using:


String text = controller.text;



3. Importing Flutter Material


For a Material Flutter application, import the Material package:


import 'package:flutter/material.dart';



4. Creating a TextEditingController


A controller is normally created as a field of a StatefulWidget when the controller needs to live for the lifetime of that widget.


class NamePage extends StatefulWidget {
  const NamePage({super.key});

  @override
  State createState() => _NamePageState();
}

class _NamePageState extends State {
  final nameController = TextEditingController();

  @override
  void dispose() {
    nameController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: nameController,
    );
  }
}




5. Why Do We Need TextEditingController?


Without a controller, a TextField can still accept user input, but your code does not have a convenient controller object for reading or modifying that value later.


For example, this TextField accepts input:


TextField(
  decoration: InputDecoration(
    labelText: 'Name',
  ),
)

With a controller, the application can access the entered value:


final nameController = TextEditingController();

TextField(
  controller: nameController,
);

print(nameController.text);


This pattern is commonly used when a button needs to retrieve the current input. Flutter's official recipe demonstrates creating a controller, assigning it to the TextField, and reading its text property. :contentReference[oaicite:2]{index=2}




6. Reading Text Using the text Property


The text property returns the current string being edited.


final controller = TextEditingController();

String value = controller.text;


Example


ElevatedButton(
  onPressed: () {
    print(nameController.text);
  },
  child: const Text('Get Name'),
)

The current value can also be displayed on the screen:


Text(nameController.text)



7. Complete Example: Read Text


import 'package:flutter/material.dart';

class ReadTextPage extends StatefulWidget {
  const ReadTextPage({super.key});

  @override
  State createState() => _ReadTextPageState();
}

class _ReadTextPageState extends State {
  final nameController = TextEditingController();

  @override
  void dispose() {
    nameController.dispose();
    super.dispose();
  }

  void showName() {
    final name = nameController.text;

    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('Name: $name'),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Read Text'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          children: [
            TextField(
              controller: nameController,
              decoration: const InputDecoration(
                labelText: 'Enter Name',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: showName,
              child: const Text('Show Name'),
            ),
          ],
        ),
      ),
    );
  }
}




8. Setting Initial Text


You can provide initial text when creating the controller.


final nameController = TextEditingController(
  text: 'John Doe',
);

Use the controller with a TextField:


TextField(
  controller: nameController,
)

Flutter's TextEditingController(String? text) constructor supports an optional initial text value. :contentReference[oaicite:3]{index=3}




9. Changing Text Programmatically


You can assign a new value to the controller's text property.


nameController.text = 'Rahul Sharma';

The connected TextField will display the updated value.


Example


ElevatedButton(
  onPressed: () {
    nameController.text = 'John Doe';
  },
  child: const Text('Set Name'),
)

When the text property is set, the controller notifies its listeners and updates the associated text field. The API notes that directly setting text also resets the selection/composing state, so more advanced updates can use the controller's value property. :contentReference[oaicite:4]{index=4}




10. Clearing Text


The clear() method removes all text from the controller.


nameController.clear();

Clear Button Example


IconButton(
  onPressed: () {
    nameController.clear();
  },
  icon: const Icon(Icons.clear),
)

The clear() method is useful for search fields, reset buttons, and form reset functionality.




11. Creating a Search Field with Clear Button


class SearchPage extends StatefulWidget {
  const SearchPage({super.key});

  @override
  State createState() => _SearchPageState();
}

class _SearchPageState extends State {
  final searchController = TextEditingController();

  @override
  void dispose() {
    searchController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Search'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: TextField(
          controller: searchController,
          decoration: InputDecoration(
            labelText: 'Search',
            prefixIcon: const Icon(Icons.search),
            suffixIcon: IconButton(
              icon: const Icon(Icons.clear),
              onPressed: () {
                searchController.clear();
              },
            ),
            border: const OutlineInputBorder(),
          ),
        ),
      ),
    );
  }
}




12. Listening to Text Changes


A TextEditingController can notify listeners whenever its value changes. Use the addListener() method to register a callback. Flutter's official documentation demonstrates this approach for reacting to changes in a text field. :contentReference[oaicite:5]{index=5}


controller.addListener(() {
  print(controller.text);
});



13. Using addListener()


The listener can be attached inside initState().


@override
void initState() {
  super.initState();

  nameController.addListener(() {
    print(nameController.text);
  });
}


The controller should be disposed when the widget is removed:


@override
void dispose() {
  nameController.dispose();
  super.dispose();
}



14. Complete Listener Example


class ListenerExample extends StatefulWidget {
  const ListenerExample({super.key});

  @override
  State createState() => _ListenerExampleState();
}

class _ListenerExampleState extends State {
  final controller = TextEditingController();

  @override
  void initState() {
    super.initState();

    controller.addListener(_handleTextChange);
  }

  void _handleTextChange() {
    print('Current text: ${controller.text}');
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
      decoration: const InputDecoration(
        labelText: 'Enter Text',
        border: OutlineInputBorder(),
      ),
    );
  }
}




15. TextEditingController vs onChanged


Both onChanged and TextEditingController can be used to respond to text changes. Flutter's documentation describes onChanged as the simpler approach, while a controller provides more control and can also read, modify, clear, and observe the field's value. :contentReference[oaicite:6]{index=6}











FeatureonChangedTextEditingController
Detect text changesYesYes
Read current textCallback provides itcontroller.text
Set text programmaticallyNo direct controller accessYes
Clear textNoYes
Listen from multiple placesLimitedYes
Access selectionNo direct accessYes
Control field valueLimitedYes



16. Accessing Selection


TextEditingController also exposes the current text selection through the selection property.


TextSelection selection = controller.selection;

This is useful when an application needs to know which portion of the text is currently selected.




17. Selecting All Text


You can select all text using the controller's selection property.


controller.selection = TextSelection(
  baseOffset: 0,
  extentOffset: controller.text.length,
);

For example:


ElevatedButton(
  onPressed: () {
    controller.selection = TextSelection(
      baseOffset: 0,
      extentOffset: controller.text.length,
    );
  },
  child: const Text('Select All'),
)



18. Moving the Cursor


You can position the cursor programmatically by changing the selection.


controller.selection = TextSelection.collapsed(
  offset: controller.text.length,
);

This places the cursor at the end of the current text.




19. Understanding TextEditingValue


TextEditingController stores a TextEditingValue. This value contains more information than just the text, including the current text and selection.


TextEditingValue currentValue = controller.value;

The controller's value property provides the complete current editing state. :contentReference[oaicite:7]{index=7}




20. Updating the value Property


For advanced use cases, you can update the complete editing value.


controller.value = TextEditingValue(
  text: 'Hello Flutter',
  selection: TextSelection.collapsed(
    offset: 13,
  ),
);

This approach is useful when you need to update text and selection together.




21. Text Property vs Value Property







PropertyPurpose
controller.textGets or sets the current text.
controller.valueGets or sets the complete TextEditingValue.
controller.selectionGets or sets the current selection.



22. Disposing TextEditingController


When a controller is created by a StatefulWidget, it should be disposed in the dispose() method. This releases resources used by the controller. Flutter's official documentation explicitly recommends calling dispose() when the controller is no longer needed. :contentReference[oaicite:8]{index=8}


@override
void dispose() {
  controller.dispose();
  super.dispose();
}

Complete Lifecycle


class MyPage extends StatefulWidget {
  const MyPage({super.key});

  @override
  State createState() => _MyPageState();
}

class _MyPageState extends State {
  final controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    controller.addListener(_listener);
  }

  void _listener() {
    print(controller.text);
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
    );
  }
}




23. TextEditingController with TextField


final nameController = TextEditingController();

TextField(
  controller: nameController,
  decoration: const InputDecoration(
    labelText: 'Full Name',
    border: OutlineInputBorder(),
  ),
)


The relationship is:


User Input
    ↓
TextField
    ↓
TextEditingController
    ↓
controller.text
    ↓
Application Logic



24. TextEditingController with TextFormField


A controller can also be supplied to TextFormField.


final emailController = TextEditingController();

TextFormField(
  controller: emailController,
  decoration: const InputDecoration(
    labelText: 'Email',
    border: OutlineInputBorder(),
  ),
)


TextFormField integrates a TextField with a surrounding Form, making it useful when validation and form state are required. :contentReference[oaicite:9]{index=9}




25. TextEditingController with Form Validation


class LoginForm extends StatefulWidget {
  const LoginForm({super.key});

  @override
  State createState() => _LoginFormState();
}

class _LoginFormState extends State {
  final formKey = GlobalKey();
  final emailController = TextEditingController();

  @override
  void dispose() {
    emailController.dispose();
    super.dispose();
  }

  void submitForm() {
    if (formKey.currentState!.validate()) {
      print('Email: ${emailController.text}');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: formKey,
      child: Column(
        children: [
          TextFormField(
            controller: emailController,
            decoration: const InputDecoration(
              labelText: 'Email',
              border: OutlineInputBorder(),
            ),
            validator: (value) {
              if (value == null || value.trim().isEmpty) {
                return 'Please enter your email';
              }
              return null;
            },
          ),
          const SizedBox(height: 20),
          ElevatedButton(
            onPressed: submitForm,
            child: const Text('Submit'),
          ),
        ],
      ),
    );
  }
}




26. Multiple TextEditingControllers


Real-world forms often require multiple controllers.


final nameController = TextEditingController();
final emailController = TextEditingController();
final phoneController = TextEditingController();
final passwordController = TextEditingController();

Connect them to individual fields:


TextField(
  controller: nameController,
)

TextField(
  controller: emailController,
)

TextField(
  controller: phoneController,
)

TextField(
  controller: passwordController,
  obscureText: true,
)




27. Complete Registration Example


import 'package:flutter/material.dart';

class RegistrationPage extends StatefulWidget {
  const RegistrationPage({super.key});

  @override
  State createState() => _RegistrationPageState();
}

class _RegistrationPageState extends State {
  final nameController = TextEditingController();
  final emailController = TextEditingController();
  final phoneController = TextEditingController();
  final passwordController = TextEditingController();

  @override
  void dispose() {
    nameController.dispose();
    emailController.dispose();
    phoneController.dispose();
    passwordController.dispose();
    super.dispose();
  }

  void registerUser() {
    final name = nameController.text.trim();
    final email = emailController.text.trim();
    final phone = phoneController.text.trim();
    final password = passwordController.text;

    if (name.isEmpty ||
        email.isEmpty ||
        phone.isEmpty ||
        password.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Please fill all fields'),
        ),
      );
      return;
    }

    print('Name: $name');
    print('Email: $email');
    print('Phone: $phone');
    print('Password: $password');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Registration'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(20),
        child: Column(
          children: [
            TextField(
              controller: nameController,
              textCapitalization: TextCapitalization.words,
              decoration: const InputDecoration(
                labelText: 'Full Name',
                prefixIcon: Icon(Icons.person),
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: emailController,
              keyboardType: TextInputType.emailAddress,
              decoration: const InputDecoration(
                labelText: 'Email',
                prefixIcon: Icon(Icons.email),
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: phoneController,
              keyboardType: TextInputType.phone,
              decoration: const InputDecoration(
                labelText: 'Phone',
                prefixIcon: Icon(Icons.phone),
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: passwordController,
              obscureText: true,
              decoration: const InputDecoration(
                labelText: 'Password',
                prefixIcon: Icon(Icons.lock),
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 24),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: registerUser,
                child: const Text('Register'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}




28. Clearing Multiple Controllers


You can clear all fields when the user taps a Reset button.


void clearForm() {
  nameController.clear();
  emailController.clear();
  phoneController.clear();
  passwordController.clear();
}

Use it with a button:


ElevatedButton(
  onPressed: clearForm,
  child: const Text('Reset'),
)



29. Controller with Search and Live Results


A controller is useful when the application needs to read the search value and perform an action whenever the value changes.


class SearchExample extends StatefulWidget {
  const SearchExample({super.key});

  @override
  State createState() => _SearchExampleState();
}

class _SearchExampleState extends State {
  final searchController = TextEditingController();
  String searchText = '';

  @override
  void initState() {
    super.initState();
    searchController.addListener(updateSearch);
  }

  void updateSearch() {
    setState(() {
      searchText = searchController.text;
    });
  }

  @override
  void dispose() {
    searchController.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        TextField(
          controller: searchController,
          decoration: const InputDecoration(
            labelText: 'Search',
            prefixIcon: Icon(Icons.search),
            border: OutlineInputBorder(),
          ),
        ),
        const SizedBox(height: 20),
        Text('Searching for: $searchText'),
      ],
    );
  }
}


Flutter's official documentation describes this listener-based approach as one way to detect changes using a TextEditingController. :contentReference[oaicite:10]{index=10}




30. Avoiding Listener Loops


Be careful when modifying controller.text or controller.value from inside a controller listener. Because changing the controller can notify listeners again, careless code can create repeated or infinite update loops. Flutter's API documentation specifically warns about this behavior. :contentReference[oaicite:11]{index=11}


For example, avoid unnecessarily doing this inside a listener:


controller.addListener(() {
  controller.text = controller.text.toUpperCase();
});

A safer approach for transformations is often to use input formatters or carefully manage the controller's value and selection.




31. Using TextEditingController with Input Formatting


For input transformations or restrictions, TextInputFormatter is often more appropriate than changing the controller from inside its own listener.


import 'package:flutter/services.dart';

TextField(
  controller: phoneController,
  keyboardType: TextInputType.phone,
  inputFormatters: [
    FilteringTextInputFormatter.digitsOnly,
  ],
  decoration: const InputDecoration(
    labelText: 'Phone Number',
    border: OutlineInputBorder(),
  ),
)




32. TextEditingController for API Data


Controllers are also useful when existing application data needs to be displayed in editable fields.


Example


final nameController = TextEditingController();

void loadUser() {
  nameController.text = 'John Doe';
}


This pattern can be used for profile-editing screens where information retrieved from an API or database needs to be displayed in TextFields.




33. Editing an Existing Profile


class EditProfilePage extends StatefulWidget {
  const EditProfilePage({super.key});

  @override
  State createState() => _EditProfilePageState();
}

class _EditProfilePageState extends State {
  final nameController = TextEditingController(
    text: 'John Doe',
  );

  final emailController = TextEditingController(
    text: '[email protected]',
  );

  @override
  void dispose() {
    nameController.dispose();
    emailController.dispose();
    super.dispose();
  }

  void saveProfile() {
    print('Name: ${nameController.text}');
    print('Email: ${emailController.text}');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Edit Profile'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          children: [
            TextField(
              controller: nameController,
              decoration: const InputDecoration(
                labelText: 'Name',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: emailController,
              keyboardType: TextInputType.emailAddress,
              decoration: const InputDecoration(
                labelText: 'Email',
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: saveProfile,
              child: const Text('Save Profile'),
            ),
          ],
        ),
      ),
    );
  }
}




34. TextEditingController Methods and Properties












Property/MethodPurpose
textGets or sets the current text.
valueGets or sets the complete TextEditingValue.
selectionGets or sets the selected text range.
clear()Removes all text.
addListener()Registers a listener for changes.
removeListener()Removes a previously registered listener.
dispose()Releases controller resources.
hasListenersIndicates whether listeners are registered.

These properties and methods are part of the Flutter TextEditingController API. :contentReference[oaicite:12]{index=12}




35. TextEditingController Lifecycle


A typical controller lifecycle in a StatefulWidget looks like this:



  1. Create the controller.

  2. Connect it to the TextField.

  3. Optionally add listeners.

  4. Read or modify its value during the widget's lifetime.

  5. Dispose the controller when the widget is removed.


class MyWidget extends StatefulWidget {
  const MyWidget({super.key});

  @override
  State createState() => _MyWidgetState();
}

class _MyWidgetState extends State {
  final controller = TextEditingController();

  @override
  void initState() {
    super.initState();
    controller.addListener(_onTextChanged);
  }

  void _onTextChanged() {
    print(controller.text);
  }

  @override
  void dispose() {
    controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: controller,
    );
  }
}




36. Common Mistakes


Mistake 1: Not Disposing the Controller


Always dispose controllers that your StatefulWidget owns.


@override
void dispose() {
  controller.dispose();
  super.dispose();
}

Mistake 2: Creating Controllers Inside build()


Avoid creating a new controller every time the build() method executes.


Incorrect:


Widget build(BuildContext context) {
  final controller = TextEditingController();
  return TextField(controller: controller);
}

Instead, create the controller as a State field:


final controller = TextEditingController();

Mistake 3: Forgetting to Connect the Controller


Creating a controller alone does not connect it to a TextField.


TextField(
  controller: controller,
)

Mistake 4: Using a Listener Without Considering Its Lifecycle


If you register listeners, make sure the controller itself is properly managed and disposed with the widget.


Mistake 5: Modifying the Controller Carelessly Inside Its Listener


Changing the controller from its own listener can trigger another notification and may create an update loop. :contentReference[oaicite:13]{index=13}




37. TextEditingController vs GlobalKey Form








ConceptPurpose
TextEditingControllerRead and control the value of an individual text field.
GlobalKeyAccess and validate/save/reset a Form.
TextFormFieldCreate a text input field that integrates with Form.
FormGroup and manage multiple form fields.

Flutter's Form API provides methods for saving, resetting, and validating form fields, while the TextEditingController focuses on controlling an individual text field's editing value. :contentReference[oaicite:14]{index=14}




38. Practical Login Example


class LoginPage extends StatefulWidget {
  const LoginPage({super.key});

  @override
  State createState() => _LoginPageState();
}

class _LoginPageState extends State {
  final emailController = TextEditingController();
  final passwordController = TextEditingController();

  @override
  void dispose() {
    emailController.dispose();
    passwordController.dispose();
    super.dispose();
  }

  void login() {
    final email = emailController.text.trim();
    final password = passwordController.text;

    if (email.isEmpty || password.isEmpty) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(
          content: Text('Please enter email and password'),
        ),
      );
      return;
    }

    print('Email: $email');
    print('Password: $password');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Login'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(20),
        child: Column(
          children: [
            TextField(
              controller: emailController,
              keyboardType: TextInputType.emailAddress,
              decoration: const InputDecoration(
                labelText: 'Email',
                prefixIcon: Icon(Icons.email),
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 16),
            TextField(
              controller: passwordController,
              obscureText: true,
              decoration: const InputDecoration(
                labelText: 'Password',
                prefixIcon: Icon(Icons.lock),
                border: OutlineInputBorder(),
              ),
            ),
            const SizedBox(height: 24),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: login,
                child: const Text('Login'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}




39. Practical Exercise


Create a Flutter profile-editing application using TextEditingController.


Required Fields



  • Full Name

  • Email

  • Phone Number

  • Address

  • About Me


Requirements



  • Create a separate TextEditingController for each field.

  • Display initial user information in the fields.

  • Allow the user to edit the information.

  • Add a Save button.

  • Read all values using controller.text.

  • Add a Reset button using controller.clear() or appropriate initial values.

  • Dispose all controllers properly.




40. Interview Questions



  1. What is TextEditingController in Flutter?

  2. Why do we use TextEditingController?

  3. How do you retrieve text from a TextField using a controller?

  4. What is the purpose of the text property?

  5. How do you set initial text in a TextField?

  6. How do you change TextField text programmatically?

  7. How do you clear a TextField?

  8. What is the purpose of addListener()?

  9. Why should TextEditingController be disposed?

  10. Where should a controller normally be created in a StatefulWidget?

  11. What is the difference between onChanged and TextEditingController?

  12. What is TextEditingValue?

  13. What is the purpose of the selection property?

  14. How can you move the cursor to the end of the text?

  15. Can TextEditingController be used with TextFormField?

  16. How can multiple controllers be used in a registration form?

  17. What happens if a controller is modified inside its own listener?

  18. How can TextEditingController be used in a search field?

  19. How can a controller be used to populate an edit-profile form?

  20. What is the difference between TextEditingController and GlobalKey?




41. Quick Revision














TaskCode
Create controllerTextEditingController()
Connect to TextFieldcontroller: myController
Read textmyController.text
Set textmyController.text = 'Hello'
Clear textmyController.clear()
Add listenermyController.addListener(...)
Read selectionmyController.selection
Read complete valuemyController.value
DisposemyController.dispose()
Initial textTextEditingController(text: 'Hello')



42. Key Takeaways



  • TextEditingController provides programmatic control over editable text.

  • Connect the controller to a TextField using the controller property.

  • Use controller.text to retrieve the current input.

  • Use controller.text = '...' to set text programmatically.

  • Use controller.clear() to remove the current text.

  • Use addListener() to respond to controller changes.

  • Use selection to work with the current cursor or selected text.

  • Use value when you need to work with the complete editing state.

  • Dispose controllers when they are no longer needed.

  • Controllers can be used with both TextField and TextFormField.

  • For form validation, combine TextFormField, Form, and appropriate validation logic.




43. Official Flutter Resources





44. JustAcademy Flutter Resources



whatsapp